﻿using System;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;

namespace RayDen.Library.Components.Surface
{
    [Serializable]
    public class PhongMaterial : SurfaceBsdf
    {
        protected RgbSpectrum Kd, Ks;
        protected float exp;

        protected static Vector sample_phong_lobe(float samplex, float sampley, float exponent, out float pdf, out float bdf,
                                                  ref Vector U, ref Vector V, ref Vector W)
        {

            float cos_theta = (float)Math.Pow(sampley, 1.0f / (exponent + 1.0f));

            float phi = samplex * MathLab.M_2PI;

            float sin_theta = MathLab.Sqrt(1.0f - cos_theta * cos_theta);

            float x = MathLab.Cos(phi) * sin_theta;
            float y = MathLab.Sin(phi) * sin_theta;
            float z = cos_theta;




            float powered_cos = (float)Math.Pow(cos_theta, exponent);
            pdf = (exponent + 1.0f) / (MathLab.M_2PI) * powered_cos;
            bdf = (exponent + 2.0f) / (MathLab.M_2PI) * powered_cos;


            return x * U + y * V + z * W;
        }

        float get_phong_lobe_pdf(float exponent, ref Normal normal, ref Vector dir_out,
                                 ref Vector dir_in, out float bdf_val)
        {


            var r = -reflect(ref dir_out, ref normal);
            float cos_theta = Math.Abs(Vector.Dot(ref r, ref dir_in));
            float powered_cos = (float)MathLab.Pow(cos_theta, exponent);

            bdf_val = (exponent + 2.0f) / (MathLab.M_2PI) * powered_cos;
            return (exponent + 1.0f) / (MathLab.M_2PI) * powered_cos;
        }

        private Vector reflect(ref Vector di, ref Normal shadeN)
        {
            return new Vector(-di.x, -di.y, di.z);
            
        }


        public PhongMaterial(RgbSpectrum kd, RgbSpectrum ks, float exp)
        {
            Ks = ks * 0.45f;
            Kd = kd * 0.45f;
            this.exp = exp;

        }


        public override void f(ref Vector lwo, ref Vector lwi, ref Normal N, ref RgbSpectrum in_fs, out RgbSpectrum fs)
        {
            CreateFrame(ref N);
            var wo = WorldToLocal(ref lwo);
            var wi = WorldToLocal(ref lwi);
            fs = EvalBrdf(ref wo, ref wi, ref N);
        }

        RgbSpectrum EvalBrdf(ref Vector wo, ref Vector wi, ref Normal N)
        {
            var r = -reflect(ref wo, ref N);
            float cos_theta = Math.Abs(Vector.Dot(ref r, ref wi));
            return (Kd * MathLab.INVPI) + ((Ks * (exp + 2f)) * MathLab.INVTWOPI) * (float)MathLab.Pow(cos_theta, exp);
        }

        protected override void EvalParams(ref SurfaceTextureData surfaceData)
        {
            Kd = RgbSpectrum.Max(surfaceData.Diffuse*0.45f, Kd);
            Ks = RgbSpectrum.Max(surfaceData.Specular*0.45f, Ks);
            exp = surfaceData.Exponent;
        }

        public override void Sample_f(ref Vector wo, ref Normal N, ref Normal shadeN, ref RgbSpectrum in_f, float u0, float u1, float u2, ref SurfaceTextureData surfaceData, out BsdfSampleData result)
        {
            result.Lambda = 0f;

            EvalParams(ref surfaceData);

            CreateFrame(ref shadeN);
            float bdf;
            var wi = sample_phong_lobe(u0, u1, exp, out result.Pdf, out bdf, ref nn, ref sn, ref tn);
            
            result.Wi = LocalToWorld(ref wi);
            result.Type = BrdfType.Glossy | BrdfType.Diffuse;
            result.F =  EvalBrdf(ref wo, ref wi, ref shadeN);
        }

        public override BrdfType Type
        {
            get { return BrdfType.Glossy | BrdfType.Diffuse; }
        }

        public override bool IsDiffuse()
        {
            return true;
        }

        public override bool IsSpecular()
        {
            return false;
        }
    }
}